package script

import (
	"errors"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/util/gconv"
	"snake/app/constants"
	"snake/app/model/script"
	"snake/app/service/log"
	ops_svc "snake/app/service/ops"
	"snake/library"
	"snake/library/base"
	"snake/app/model/ops"
	"fmt"
	"snake/app/utils"
	"github.com/gogf/gf/frame/g"
	"sync"
	"strconv"
	//"bufio"
	"io"
	"context"
	"time"
)

// 请求参数
type Request struct {
	script.Entity
	UserId int `form:"userId" json:"userId"`
}

// 通过id获取实体
func GetById(id int64) (*script.Entity, error) {
	if id <= 0 {
		glog.Error(" get id error")
		return new(script.Entity), errors.New("参数不合法")
	}

	return script.Model.FindOne(" id = ?", id)
}

// 通过id获取实体
func GetContextById(id int64) (string, error) {
	if id <= 0 {
		glog.Error(" get id error")
		return "", errors.New("参数不合法")
	}
	entity, err := script.Model.FindOne(" id = ?", id)
	return entity.Context, err
}

// 根据条件获取实体
func GetOne(form *base.BaseForm) (*script.Entity, error) {
	where := " 1 = 1 "
	var params []interface{}
	if form.Params != nil && form.Params["id"] != "" {
		where += " and id = ? "
		params = append(params, gconv.Int(form.Params["id"]))
	}

	return script.Model.FindOne(where, params)
}

// 删除实体
func Delete(id int64, userId int) (int64, error) {
	if id <= 0 {
		glog.Error("delete id error")
		return 0, errors.New("参数不合法")
	}

	// 获取删除对象
	entity, err := GetById(id)
	if err != nil {
		return 0, err
	}
	entity.UpdateId = userId
	entity.UpdateTime = library.GetNow()

	r, err1 := script.Model.Delete(" id = ?", id)
	if err1 != nil {
		return 0, err1
	}

	log.SaveLog(entity, constants.DELETE)
	return r.RowsAffected()
}

// 保存实体
func Save(request *Request) (int64, error) {
	entity := (*script.Entity)(nil)
	err := gconv.Struct(request.Entity, &entity)
	if err != nil {
		return 0, errors.New("数据错误")
	}
	entity.UpdateId = request.UserId
	entity.UpdateTime = library.GetNow()
	// 判断新增还是修改
	if entity.Id <= 0 {
		entity.CreateId = request.UserId
		entity.CreateTime = library.GetNow()
		// glog.Info("新增数据:", entity)
		r, err := script.Model.Insert(entity)
		if err != nil {
			glog.Error("新增数据失败:", err)
			return 0, err
		}
		// 回写主键
		lastId, err := r.LastInsertId()
		if err != nil {
			glog.Error(err)
			return 0, err
		}
		entity.Id = gconv.Int(lastId)

		log.SaveLog(entity, constants.INSERT)
		return r.RowsAffected()
	} else {
		// glog.Debug("更新数据:", entity)
		r, err := script.Model.OmitEmpty().Where(" id = ?", entity.Id).Update(entity)
		if err != nil {
			glog.Error(err)
			return 0, err
		}
		log.SaveLog(entity, constants.UPDATE)
		return r.RowsAffected()
	}
}

// 列表数据查询
func List(form *base.BaseForm) ([]*script.Entity, error) {
	where := " 1 = 1 "
	var params []interface{}
	if form.Params != nil && form.Params["name"] != "" {
		where += " and name like ? "
		params = append(params, "%"+form.Params["name"]+"%")
	}
	if form.Params != nil && form.Params["parentId"] != "" {
		where += " and parent_id = ? "
		params = append(params, gconv.Int(form.Params["parentId"]))
	}

	return script.Model.Order(form.OrderBy).FindAll(where, params)
}

// 分页查询
func Page(form *base.BaseForm) ([]script.Entity, error) {
	if form.Page <= 0 || form.Rows <= 0 {
		glog.Error("page param error", form.Page, form.Rows)
		return []script.Entity{}, nil
	}

	where := " 1 = 1 "
	var params []interface{}
	if form.Params != nil {
		if form.Params["name"] != "" {
			where += " and t.name like ? "
			params = append(params, "%"+form.Params["name"]+"%")
		}
	}
	num, err := script.Model.As("t").FindCount(where, params)
	form.TotalSize = num
	form.TotalPage = num / form.Rows

	if err != nil {
		glog.Error("page count error", err)
		return []script.Entity{}, err
	}

	// 没有数据直接返回
	if num == 0 {
		form.TotalPage = 0
		form.TotalSize = 0
		return []script.Entity{}, err
	}

	var resData []script.Entity
	dbModel := script.Model.As("t").Fields(script.Model.Columns() + ",su1.real_name as updateName,su2.real_name as createName")
	dbModel = dbModel.LeftJoin("sys_user su1", " t.update_id = su1.id ")
	dbModel = dbModel.LeftJoin("sys_user su2", " t.update_id = su2.id ")
	err = dbModel.Where(where, params).Order(form.OrderBy).Page(form.Page, form.Rows).M.Structs(&resData)
	if err != nil {
		glog.Error("page list error", err)
		return []script.Entity{}, err
	}

	return resData, nil
}

//执行脚本
func RunJob(job *ops.Job) {
	request := ops_svc.Request{}
	request.Job = job
	// write New Job into database
	_, err := ops_svc.Save(&request)
	if err != nil {
		glog.Error("保存任务失败：", err.Error())
		return
	}
	//声明WaitGroup
	var wg sync.WaitGroup
	//声明上下文
	ctx, cancel := context.WithCancel(context.Background())
	//声明一个goroutine, 用于接收任务执行完成信号
	ch := make(chan bool, len(job.Tasks))
	defer close(ch)
	//完成的任务数量
	finishCount := 0
	//获取脚本实例
	scriptEntity,err := GetById(int64(job.ScriptId))
	if err != nil {
		glog.Error("获取脚本实例失败：", err)
		return
	}
	scriptLang := scriptEntity.ScriptLang
	execTimeout := scriptEntity.Timeout
	//平台服务的IP，端口
	serverIp := g.Config().GetString("server.Ip")
	serverPort := g.Config().GetString("server.Address")
	// record start time
	job.StartTime = library.GetNow()
	//任务执行状态
	job.Status = "running"
	//存放任务中间状态，默认为true
	isJobSucc := true
	//启动n个goroutine执行任务
	for i:= 0; i<len(job.Tasks); i++ {
		task := &job.Tasks[i]
		//登记goroutine +1
		wg.Add(1)
		go func(task *ops.Task) {
			defer wg.Done()
			host := task.Host
			user := gconv.String(host["signinUser"])
			password := gconv.String(host["signinPassword"])
			ip := gconv.String(task.Host["ipaddr"])
			port := gconv.Int(task.Host["signinPort"])
			//创建ssh-client
			sshClient,err := utils.DialSSH(user, password, "", ip, port)
			if err != nil {
				task.Status = "faild"
				task.Log = err.Error()
				ch <- false
				glog.Error("创建ssh client 失败",err.Error())
				return
			}
			defer sshClient.Close()
			//创建ssh-session
			session, err := sshClient.NewSession()
			if err != nil {
				task.Status = "faild"
				task.Log = err.Error()
				ch <- false
				glog.Error("创建ssh session 失败",err.Error())
				return
			}
			defer session.Close()
			//io.reader
			stdout, err := session.StdoutPipe()
			if err != nil {
				task.Status = "faild"
				task.Log = "创建ssh StdoutPipe 失败:" + err.Error()
				ch <- false
				glog.Error("创建ssh StdoutPipe 失败:", err.Error())
				return
			}

			stderr, err := session.StderrPipe()
			if err != nil {
				task.Status = "faild"
				task.Log = "创建ssh StderrPipe 失败:" + err.Error()
				ch <- false
				glog.Error("创建ssh StderrPipe 失败:", err.Error())
				return
			}
			//执行远程命令, 默认连接超时时间10s
			cmd := ""
			if scriptLang == "shell" {
				cmd = fmt.Sprintf("curl -XGET http://%s%s/download/script/%d  --connect-timeout 10 -o /tmp/%s.sh && set -o pipefail && timeout -s 9 %s sh /tmp/%s.sh | tee /tmp/%s.log", serverIp, serverPort, task.ScriptId, job.JobId, strconv.Itoa(execTimeout), job.JobId, job.JobId)
			} else if scriptLang == "python" {
				cmd = fmt.Sprintf("curl -XGET http://%s%s/download/script/%d  --connect-timeout 10 -o /tmp/%s.py && set -o pipefail && timeout -s 9 %s python /tmp/%s.py | tee /tmp/%s.log", serverIp, serverPort, task.ScriptId, job.JobId, strconv.Itoa(execTimeout), job.JobId, job.JobId)
			} else {
				cmd = fmt.Sprintf("curl -XGET http://%s%s/download/script/%d  --connect-timeout 10 -o /tmp/%s.go && set -o pipefail && timeout -s 9 %s go run /tmp/%s.go | tee /tmp/%s.log", serverIp, serverPort, task.ScriptId, job.JobId, strconv.Itoa(execTimeout), job.JobId, job.JobId)
			}
			glog.Info(ip, " run cmd: ", cmd)
			task.Status = "running"
			//执行远程命令
			session.Start(cmd)
			//stdoutReader := bufio.NewReader(stdout)
			//stderrReader := bufio.NewReader(stderr)
			errMsg := ""
			LOOP:
			for {
				by2 := make([]byte, 1024)
				by3 := make([]byte, 1024)
				n2, err2 := stdout.Read(by2)
				n3, err3 := stderr.Read(by3)
				if n2 > 0 {  
					task.Log += string(by2[:n2]) + "\n"
				}
				if n3 > 0 {
					errMsg += string(by3[:n3]) + "\n"
				}

				//stdoutLine, err2 := stdoutReader.ReadString('\n')
				//stderrLine, err3 := stderrReader.ReadString('\n')
				// if stdoutLine != "" {
				// 	task.Log += stdoutLine + "\n"
				// }
				// if stderrLine != "" {
				// 	errMsg += stderrLine
				// }
				if err2 == io.EOF && err3 == io.EOF {
					break LOOP
				}
			}
			err = session.Wait()
			if err != nil {
				task.Status = "faild"
				task.Log += errMsg + "\n" + err.Error()
				glog.Error("远程执行cmd失败: ", err)
				ch <- false
			} else {
				task.Status = "succeed"
				ch <- true
			}
		}(task)
	}

	//启动一个goroutine, 定时回写任务日志 5s
	wg.Add(1)
	go func(ctx context.Context) {
		LOOP:
		for {
			ops_svc.Save(&request)
			time.Sleep(5 * time.Second)
			select {
			case <-ctx.Done(): // 等待上级通知
				break LOOP
			default:
			}
		}
		wg.Done()
	}(ctx)

	LOOP:
	for {
		//等待task执行完成，如果没有task执行完成，chan为空，这里会阻塞
		succ, ok := <- ch
		if ok {
			finishCount += 1
			//有一个task失败则任务失败
			if succ != true {
				isJobSucc = false
			}
		}
		//如果所有任务都执行完，退出所有goroutine
		if finishCount == len(job.Tasks) {
			cancel() // 通知子goroutine结束
			break LOOP
		}
	}
	//回写Job状态
	if isJobSucc {
		job.Status = "succeed"
	} else {
		job.Status = "faild"
	}
	// record end time
	job.EndTime = library.GetNow()
	wg.Wait() // 等待所有登记的goroutine都结束
	//最后再更新一次数据库
	ops_svc.Save(&request)
}